<?php
/**
 * Zead
 *
 * LICENSE	
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://www.opensource.org/licenses/bsd-license.php
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to eu@marcelomx.com so we can send you a copy immediately.
 *
 * @category   Zead
 * @author     Marcelo Rodrigues Gonzaga <eu@marcelomx.com>
 * @copyright  Copyright (c) 2008 Marcelo Rodrigues Gonzaga
 * @link       http://code.google.com/p/zead 
 * @license    http://www.opensource.org/licenses/bsd-license.php     New BSD License
 *
 **/


/** @see Zead_Xml_Abstract **/
require_once 'Zead/Xml/Abstract.php';

/** @see Zead_Html_Element **/
require_once 'Zead/Html/Element.php';

/** @see Zead_Html_Renderer **/
require_once 'Zead/Html/Renderer.php';

/**
 * Abstract class Zead_Html_Abstract 
 * 
 * @category   Zead
 * @package    Zead_Html
 * @copyright  Copyright (c) 2008 Marcelo Rodrigues Gonzaga 
 * @license    http://www.opensource.org/licenses/bsd-license.php     New BSD License  
 */
abstract class Zead_Html_Abstract extends Zead_Xml_Abstract
{
	/**
	 * Se irá usar a extensão TIDY para formatar o Zead_Html
	 * @var bool
	 */
	public $formatTidy = false;
	
	/**
	 * Se irá preservar todas as instruções de processo, geralmente
	 * utilizadas por linguagens de script como o php.
	 * @var bool
	 */
	public $preserveProcessingInstruction = true;

	/**
	 * Lista de diretórios onde os filtros estão localizados
	 * @var array
	 */
	protected $_helperPaths = array();
	
	/** @var array **/
	protected $_helperClass = array();

	/**
	 * Lista de diretórios onde os filtros estão localizados
	 * @var array
	 */
	protected $_filterPaths = array();

	/** 
	 * A lista de filtros a serem utilizados ao salvar o Zead_Html
	 * @var array
	 */
	protected $_filters = array();

	/**
	 * A lista de objetos criados a partir dos nomes de filtros
	 * Os objetos são criados e reutilizados quando necessario.
	 * @var array
	 */
	protected $_filterClass = array();
	
	
	/**
	 * O nome da classe do renderizador.
	 * @var Zead_Html_Renderer
	 */
	protected $_rendererClassName = 'Zead_Html_Renderer';
	
	/**
	 * O objeto renderizador
	 * @var Zead_Html_Renderer
	 */
	protected $_renderer = null;

	
	/**
 	 * Class constructor
	 * @param string $version   A versão do XML
	 * @param string $encoding  O encoding
	 */
	public function __construct($version = '1.0', $encoding = 'utf8')
	{
		parent::__construct($version, $encoding);
				
		$this->registerNodeClass('DOMElement', 'Zead_Html_Element');
		
		// Creating the documentElement
		$this->appendChild($this->createElement('html'));
		
		// Criando o renderizador de arquivo.
		$this->_renderer = new Zead_Html_Renderer();

		// Adicionando os paths para os helpers e filtros
		$this->addHelperPath(dirname(__FILE__) . '/Helper');
	}
	
	/**
	 * Método mágico para as chamadas dos Helpers
	 * @param string $method
	 * @param array  $args
	 * @return mixed
	 */
	public function __call($method, $args)
	{
		// Retorna o elemento HEAD ou BODY, de acordo com o método passado.
		if (preg_match('/^(body|head)$/', $method, $match)) {
			$list = $this->documentElement->select($match[1]);

			if ($list->count())
				return $list->current();
			else
				return $this->documentElement->appendChild($this->createElement($match[1]));
		}

		// Chamando metodo select do documentElement
		if ($method == 'select')
			return call_user_func_array(array($this->documentElement, 'select'), (array) $args);
	
		// Chegou aqui, então aplicar um helper chamado.
		foreach ($this->_helperPaths as $path) {
			$className = $path['prefix'] . ucfirst($method);
			
			if (isset($this->_helperClass[$className])) {
				$helper = $this->_helperClass[$className];
			} else {
				$fileName = $path['dir'] . DIRECTORY_SEPARATOR . ucfirst($method) .'.php';
				require_once $fileName;

				if ($className != 'Zead_Html_Helper_Abstract' && class_exists($className)) {
					$helper = new $className();
					$helper->setHtml($this);
					$this->_helperClass[$className] = $helper;
				}
			}
			if (method_exists($helper, $method)) {
				return call_user_func_array(array($helper,$method), (array) $args);
			}
		}
	}

	/**
	 * Recupera um atributo aplicado ao renderizador (template)
	 * @param string $key O atributo.
	 * @return mixed
	 */	
	public function __get($key) {
		return $this->_renderer->$key;
	}
	
	/**
	 * Cria um novo atributo com valor a ser recuperado no momento da renderização.
	 * @param string $key   O nome do atributo
	 * @param mixed  $value O valor do atributo.
	 */	
	public function __set($key, $value) {
		$this->_renderer->$key = $value;
	}
	
	public function __sleep() 
	{
		return array('_formatTidy','_preserveProcessingInstruction',
			'_helperPaths','_filterPaths',
			'_filterClass','_renderClassName','_renderer');
	}
	
	/**
	 * Retorna o objeto renderizador utilizado.
	 *
	 * @return Zead_Html_Renderer
	 */
	public function getRenderer() 
	{
		return $this->_renderer;
	}
	
	/**
	 * Seta o objeto renderizador
	 * 
	 * @param Zead_Html_Renderer O objeto renderizador.
	 */
	public function setRenderer(Zead_Html_Renderer $renderer = null) 
	{
		if (!$renderer instanceof Zead_Html_Renderer)
			throw new Zead_Html_Exception('The object renderer isnt a valida Zead_Html_Renderer instance');
			
		$this->_renderer = $renderer;
	}

	/**
	 * Adiciona um diretorio de classes helpers
	 * @param string $path
	 * @param string $classprefix O prefixo a ser aplicada a classe do helper invocado
	 */
	public function addHelperPath($path, $classprefix = 'Zead_Html_Helper_') 
	{
		array_unshift($this->_helperPaths, array('dir' => $path, 'prefix' => $classprefix));

		return $this;
	}

	/**
	 * Adiciona um filtra a lista de filtros a serem aplicados antes do salvamento do Zead_Html.
	 * O primeiro argumento deve ser o nome do filtro, os seguintes são os argumentos
	 * que serão aplicados ao filtro especificado.
	 */
	public function addFilter() 
	{
		$args = func_get_args();
		$name = array_shift($args);

		$this->_filters[] = array('name' => $name, 'args' => $args);
	}
	
	public function setFilter()
	{
		$args = func_get_args();
		$this->_filters = array();
		call_user_func_array(array($this, 'addFilter'), $args);
		return $this;
	}	

	/**
	 * Recupera o objeto relacionado ao nome do filtro passado. 
	 * 
	 * @param string $name O nome do filtro
	 * @return Zead_Html_Filter_Abstract
	 */
	public function getFilter($name)
	{
		if (!isset($this->_filterClass[$name])) {
			$class = 'Zead_Html_Filter_'. ucfirst($name);
			$separator = DIRECTORY_SEPARATOR;
			require_once str_replace('_', '/', $class) .'.php';
			$this->_filterClass[$name] = new $class();
			$this->_filterClass[$name]->setHtml(&$this);
		}

		return $this->_filterClass[$name];
	}
	
	/**
	 * Carrega os dados a partir de um arquivo. Se php, incluirá o arquivo.
	 * 
	 * @param  string $file String contendo o caminho até o arquivo
	 * @param  bool   $eval Se o arquivo incluído será avaliado usando include.
	 * @return mixed  Falso em caso de falha, referencia para o objeto do documento, 
	 * caso verdadeiro.
	 */
	public function load($file, $eval = false) 
	{
		if (!strlen($string = $this->_renderer->render($file, $eval)))
			return false;
			
		return $this->loadHTML($string);
	}
	
	/**
	 * Overrides the method.
	 * @see Zead_Html_Abstract::load
	 */
	public function loadHTMLFile($file) 
	{
		return $this->load($file);
	}		
	
	/**
	 * Save the Zead_Html. Before, apply the filters in Zead_Html.
	 * return string The Zead_Html code
	 */
	public function saveHTML($showBodyOnly = false) 
	{
		if ($this->preserveProcessingInstruction !== true)
			$this->addFilter('processingInstruction');
	
		// Filtrando o Zead_Html
		$this->_filter();

		// Salvando o Zead_Html como string
		$html = parent::saveHTML();

		// Aplicando o formatador TIDY
		if ($this->formatTidy !== true || extension_loaded('tidy') !== true)
			return $html;

		$html = tidy_parse_string($html, array(
			'input-encoding'  => 'utf8',
			'output-encoding' => 'utf8',
			'output-xhtml'    => true,
			'add-xml-decl'	  => false,
			'drop-font-tags'  => true,
			'indent'          => true,
			'indent-spaces'   => 4,
			'wrap'            => 0,
			'preserve-entities' => true,
			'doctype'         => 'auto',
			'numeric-entities'=> true,			
			'wrap-php'        => true,
			'quote-marks'     => true,
			'drop-font-tags'  => true,
			'show-body-only'  => $showBodyOnly,
			'show-errors'     => 6,
			'show-warnings'   => true		
		));		
		tidy_clean_repair($html);

		return tidy_get_output($html);
	}
	
	/**
	 * Aplica os filtros ao salvar o Zead_Html para renderização
	 **/
	protected function _filter() 
	{	
		foreach ($this->_filters as $key => $value) {
			$filter = $this->getFilter($value['name']);
			call_user_func_array(array($filter, 'filter'), (array) $value['args']);
		}					
	}
}

